
/* GCSx
** CONSOLE.CPP
**
** Console command support
*/

/*****************************************************************************
** Copyright (C) 2003-2006 Janson
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
** 
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
*****************************************************************************/

#include "all.h"

// Command structures
enum {
    PARAM_NONE = 0,
    PARAM_STRING,
    PARAM_INT,
    PARAM_LINE, // Remainder of line as a string
};

struct ConsoleParam {
    string valueS; // Always set
    int valueI;
};

#define MAX_PARAM 3

struct ConsoleCommand {
    const char* cmd;
    int gameMode;
    int editorMode;
    int param[MAX_PARAM];
    void (*function)(ConsoleParam* params);
};

// Forward declarations
void cWorldQuery(ConsoleParam* params);
void cWorldSet(ConsoleParam* params);
void cDebugLevelSet(ConsoleParam* params);
void cDebugLevelQuery(ConsoleParam* params);
void cUndoBufferSet(ConsoleParam* params);
void cUndoBufferQuery(ConsoleParam* params);
void cMemExcept(ConsoleParam* params);
void cUndoExcept(ConsoleParam* params);
void cHelpAll(ConsoleParam* params);
void cHelpCommand(ConsoleParam* params);
void cClear(ConsoleParam* params);
void cOpenGLTest(ConsoleParam* params);
void cConfigList(ConsoleParam* params);
void cConfigQuery(ConsoleParam* params);
void cConfigSet(ConsoleParam* params);
void cTextureTest(ConsoleParam* params);
void cCompile(ConsoleParam* params);
void cDecompile(ConsoleParam* params);

// Valid commands and parameters
ConsoleCommand commands[] = {
    { "clear",      1, 1, { PARAM_NONE,                           }, cClear },
    { "compile",    0, 1, { PARAM_LINE, PARAM_NONE                }, cCompile },
    { "config",     1, 1, { PARAM_NONE,                           }, cConfigList },
    { "config",     1, 1, { PARAM_STRING, PARAM_NONE              }, cConfigQuery },
    { "config",     1, 1, { PARAM_STRING, PARAM_INT, PARAM_NONE   }, cConfigSet },
    { "config",     1, 1, { PARAM_STRING, PARAM_LINE, PARAM_NONE  }, cConfigSet },
    { "debuglevel", 1, 1, { PARAM_NONE,                           }, cDebugLevelQuery },
    { "debuglevel", 1, 1, { PARAM_INT, PARAM_NONE                 }, cDebugLevelSet },
    { "decompile",  0, 1, { PARAM_LINE, PARAM_NONE                }, cDecompile },
    { "help",       1, 1, { PARAM_NONE,                           }, cHelpAll },
    { "help",       1, 1, { PARAM_STRING, PARAM_NONE              }, cHelpCommand },
    { "opengltest", 1, 1, { PARAM_NONE,                           }, cOpenGLTest },
    { "undobuffer", 0, 1, { PARAM_NONE,                           }, cUndoBufferQuery },
    { "undobuffer", 0, 1, { PARAM_INT, PARAM_NONE                 }, cUndoBufferSet },
    { "world",      0, 1, { PARAM_NONE,                           }, cWorldQuery },
    { "world",      0, 1, { PARAM_INT, PARAM_NONE                 }, cWorldSet },
#ifdef MEMDEBUG
    { "memexcept",  1, 1, { PARAM_INT, PARAM_NONE                 }, cMemExcept },
#endif
#ifndef NDEBUG
    { "texturetest",1, 1, { PARAM_INT, PARAM_INT, PARAM_INT       }, cTextureTest },
    { "undoexcept", 0, 1, { PARAM_INT, PARAM_NONE                 }, cUndoExcept },
#endif
    // @TODO: undo, undolist, redo, redolist, undoclear, redoclear
    // @TODO: fileexcept (debug only)
    // @TODO: exit, editor, frontend
    // @TODO: history
    { NULL,         0, 0, { PARAM_NONE },     NULL },
};

// Currently selected world, required for many functions
WorldEdit* selectedWorld = NULL;

WorldEdit* verifySelectedWorld() { start_func
    if (selectedWorld) {
        if (WorldEdit::verifyWorld(selectedWorld)) return selectedWorld;
        selectedWorld = NULL;
        debugWrite("(previously active world no longer available)");
    }
    if (WorldEdit::countWorlds() == 0) {
        debugWrite("ERROR: No worlds currently open.");
        return NULL;
    }
    if (WorldEdit::countWorlds() == 1) {
        selectedWorld = WorldEdit::listWorlds(0);
        debugWrite("(selecting world \"%s\" as active)", selectedWorld->getTitle().c_str());
        return selectedWorld;
    }
    debugWrite("ERROR: No active world- use 'world' to select an active world.");
    return NULL;
}

// Command functions
void cCompile(ConsoleParam* params) { start_func
    WorldEdit* world = verifySelectedWorld();
    if (world) {
        toLower(params[0].valueS);
        ScriptEdit* script = dynamic_cast<ScriptEdit*>(world->findScriptCode(params[0].valueS));
        if (script == NULL) script = dynamic_cast<ScriptEdit*>(world->findScriptLib(params[0].valueS));
        if (script == NULL) {
            debugWrite("ERROR: No script '%s' found in active world", params[0].valueS.c_str());
        }
        else {
            // @TODO: throw_File; does this properly store the compiled form?
            script->markLock();
            script->compile();
            script->markUnlock();
        }
    }
}

// @TODO: Currently doesn't work (and disabled) during gameplay mode
void cDecompile(ConsoleParam* params) { start_func
    WorldEdit* world = verifySelectedWorld();
    if (world) {
        toLower(params[0].valueS);
        ScriptEdit* script = dynamic_cast<ScriptEdit*>(world->findScriptCode(params[0].valueS));
        if (script == NULL) script = dynamic_cast<ScriptEdit*>(world->findScriptLib(params[0].valueS));
        if (script == NULL) {
            debugWrite("ERROR: No script '%s' found in active world", params[0].valueS.c_str());
        }
        else {
            // @TODO: throw_File; does this properly store the compiled form?
            script->markLock();
            script->decompile();
            script->markUnlock();
        }
    }
}

void cOpenGLTest(ConsoleParam* params) { start_func
    int maxSize = config->readNum(OGL_MAX_SIZE);
    int minSize = config->readNum(OGL_MIN_SIZE);
    
    selectVideoMode(-1, -1, -1, -1, 0, 1);
    debugWrite("OpenGL tests-");
    
    // For simplicity- ensure test size is a power of 2, max 512
    int testSize = 1;
    while ((testSize <= maxSize) && (testSize < 1024)) {
        testSize *= 2;
    }
    testSize /= 2;
    
    // Now test every size from 512 down to 16, limited by minsize
    if (minSize < 16) minSize = 16;
    int textureCount = 1000;
    while (testSize >= minSize) {
        textureCount = oglTestPossibleTextures(testSize, testSize, 1, textureCount);
        // abort if no limit detected
        if (textureCount < 0) break;
        testSize /= 2;
        textureCount *= 4;
    }
    
    initDefaultVideo();
}

void cClear(ConsoleParam* params) { start_func
    debugClear();
}

void cWorldQuery(ConsoleParam* params) { start_func
    if (WorldEdit::countWorlds() == 0) {
        debugWrite("No worlds currently open.");
    }
    else {
        debugWrite("%d world(s) currently open:", WorldEdit::countWorlds());
        int pos = 0;
        while (WorldEdit* nextWorld = WorldEdit::listWorlds(pos++)) {
            if (nextWorld == selectedWorld) debugWrite("%d: %s (active)", pos, nextWorld->getTitle().c_str());
            else debugWrite("%d: %s", pos, nextWorld->getTitle().c_str());
        }
    }
}

void cWorldSet(ConsoleParam* params) { start_func
    WorldEdit* nextWorld = WorldEdit::listWorlds(params[0].valueI - 1);
    
    if (nextWorld == NULL) debugWrite("ERROR: No world %d open- use 'world' to list open worlds.", params[0].valueI);
    else {
        selectedWorld = nextWorld;
        debugWrite("Selecting world \"%s\" as active.", selectedWorld->getTitle().c_str());
    }
}

void cDebugLevelSet(ConsoleParam* params) { start_func
    if ((params[0].valueI >= 0) && (params[0].valueI <= DEBUG_MAX)) {
        debugWrite("Debuglevel changed to %d.", debugLevel(params[0].valueI));
    }
    else {
        debugWrite("ERROR: Invalid debuglevel- Debuglevel ranges from 0 to %d.", DEBUG_MAX);
    }
}

void cDebugLevelQuery(ConsoleParam* params) { start_func
    debugWrite("Current debuglevel is %d.", debugLevel());
}

void cUndoBufferSet(ConsoleParam* params) { start_func
    WorldEdit* world = verifySelectedWorld();
    if (world) {
        if ((params[0].valueI >= 0) && (params[0].valueI <= UndoBuffer::UNDO_MAX_BUFFER)) {
            world->undo.bufferSize(params[0].valueI);
            if (params[0].valueI) debugWrite("Undo buffer resized to %dK.", params[0].valueI);
            else debugWrite("Undo buffer has been disabled.");
        }
        else debugWrite("ERROR: Undo buffer must be between 0K and %dK.", UndoBuffer::UNDO_MAX_BUFFER);
    }
}

void cUndoBufferQuery(ConsoleParam* params) { start_func
    WorldEdit* world = verifySelectedWorld();
    if (world) {
        if (world->undo.bufferSize()) debugWrite("Undo buffer is currently %dK.", world->undo.bufferSize());
        else debugWrite("Undo buffer is currently disabled.");
    }
}

#define MAX_CONFIG_HELP 5
struct configLabel {
    const char* cmd;
    int isString;
    int code;
    const char* help;
};
configLabel configNames[] = {
    // @TODO: output help using a word-wrapping function
    { "front_alpha", 0, FRONTEND_ACTIVE_ALPHA,
      "Translucency (alpha) for front-end menus/windows when activated during gameplay. Ranges from 0 to 256. (recommended- 192 to 256)",
      },
    { "front_bkalpha", 0, FRONTEND_ACTIVE_BKALPHA,
      "Translucency (alpha) for front-end background/desktop when activated during gameplay. Ranges from 0 to 256. (recommended- 0 to 128)",
      },
    { "front_overlay", 0, FRONTEND_INACTIVE_ALPHA,
      "Translucency (alpha) for front-end menus/windows when overlaid during gameplay, while game is still active. Ranges from 0 to 256. (recommended- 128 to 192)",
      },
    { "front_overlaybk", 0, FRONTEND_INACTIVE_BKALPHA,
      "Translucency (alpha) for front-end background/desktop when overlaid during gameplay, while game is still active. Ranges from 0 to 256. (recommended- 0)",
      },
    { "texture_max", 0, OGL_MAX_SIZE,
      "Maximum height/width for OpenGL textures. This value is auto-detected and, if changed, may prevent graphics from displaying properly.",
      },
    { "texture_megagroup", 0, OGL_PREFER_GROUPING,
      "Group as many OpenGL textures as possible, even at the expense of texture memory. If off, textures are only grouped in an attempt to conserve memory. (0 = off, 1 = on)",
      },
    { "texture_min", 0, OGL_MIN_SIZE,
      "Minimum height/width for OpenGL textures. This value is auto-detected and, if changed, may prevent graphics from displaying properly.",
      },
    { "texture_powertwo", 0, OGL_POWER_OF_TWO,
      "OpenGL textures must be power-of-two sizes. This value is auto-detected and, if changed, may prevent graphics from displaying properly. (0 = off, 1 = on)",
      },
    { "texture_single", 0, OGL_FORCE_SINGLE,
      "Never group OpenGL textures together- always store single textures. If on, textures are never grouped- even when doing so would conserve texture memory. This setting overrides 'texture_megagroup'. (0 = off, 1 = on)",
      },
    { "linked_retain", 0, LINKED_RETAIN,
      "Retain linked version of bytecode in memory during gameplay. Increases memory usage while reducing scene-transition and script-loading times for scenes and scripts previously used. (0 = off, 1 = on)",
      },
    { "linked_pregen", 0, LINKED_PREGENERATE,
      "Pre-generate and retain linked version of bytecode for all scripts before gameplay. Increases game start time and memory usage while reducing all future scene-transition and script-loading times. This setting obsoletes 'linked_retain'. (0 = off, 1 = on)",
      },
    { NULL, 0, 0, NULL },
};

void cConfigList(ConsoleParam* params) { start_func
    for (int pos = 0; configNames[pos].cmd; ++pos) {
        if (configNames[pos].isString)
            debugWrite("%s: %s", configNames[pos].cmd, config->readStr(configNames[pos].code).c_str());
        else
            debugWrite("%s: %d", configNames[pos].cmd, config->readNum(configNames[pos].code));
    }
}

void cConfigQuery(ConsoleParam* params) { start_func
    for (int pos = 0; configNames[pos].cmd; ++pos) {
        if (myStricmp(params[0].valueS.c_str(), configNames[pos].cmd) == 0) {
            if (configNames[pos].isString)
                debugWrite("%s: %s", configNames[pos].cmd, config->readStr(configNames[pos].code).c_str());
            else
                debugWrite("%s: %d", configNames[pos].cmd, config->readNum(configNames[pos].code));
            debugWrite("  %s", configNames[pos].help);
            return;
        }
    }
    debugWrite("No configuration setting '%s' found. Type 'config' for a list.", params[0].valueS.c_str());
}

// @TODO: apply change for some settings (ex: front_*)
void cConfigSet(ConsoleParam* params) { start_func
    for (int pos = 0; configNames[pos].cmd; ++pos) {
        if (myStricmp(params[0].valueS.c_str(), configNames[pos].cmd) == 0) {
            if (configNames[pos].isString) {
                debugWrite("%s: Previous setting: %s", configNames[pos].cmd, config->readStr(configNames[pos].code).c_str());
                config->write(configNames[pos].code, params[1].valueS);
                debugWrite("%s: New setting: %d", configNames[pos].cmd, config->readStr(configNames[pos].code).c_str());
            }
            else {
                debugWrite("%s: Previous setting: %d", configNames[pos].cmd, config->readNum(configNames[pos].code));
                config->write(configNames[pos].code, params[1].valueI);
                debugWrite("%s: New setting: %d", configNames[pos].cmd, config->readNum(configNames[pos].code));
            }
            return;
        }
    }
    debugWrite("No configuration setting '%s' found. Type 'config' for a list.", params[0].valueS.c_str());
}

#ifdef MEMDEBUG
void cMemExcept(ConsoleParam* params) { start_func
    if (params[0].valueI >= 0) {
        // Turn off to debug write
        setMemThreshold(0);
        if (params[0].valueI) debugWrite("Memory fail threshold set to %d bytes.", params[0].valueI);
        else debugWrite("Memory fail threshold disabled.");
        setMemThreshold(params[0].valueI);
    }
    else debugWrite("ERROR: Memory fail threshold must be a positive value or zero.");
}
#endif

#ifndef NDEBUG
void cUndoExcept(ConsoleParam* params) { start_func
    if (params[0].valueI >= 0) {
        UndoBuffer::setUndoThreshold(params[0].valueI);
        if (params[0].valueI) debugWrite("Undo fail threshold set to %d item(s).", params[0].valueI);
        else debugWrite("Undo fail threshold disabled.");
    }
    else debugWrite("ERROR: Undo fail threshold must be a positive value or zero.");
}

void cTextureTest(ConsoleParam* params) { start_func
    int w = params[0].valueI;
    int h = params[1].valueI;
    int count = params[2].valueI;
    int max = config->readNum(OGL_MAX_SIZE);
    
    if ((w <= 0) || (h <= 0) || (count <= 0))
        debugWrite("ERROR: All parameters must be greater than zero.");
    else if ((w > max) || (h > max))
        debugWrite("ERROR: Size is larger than maximum texture size.");
    else {
        debugWrite("Storing %d graphics (%d x %d)", count, w, h);
        vector<TextureMap::tGroup> sizes;
        TextureMap::groupTextures(count, w, h, sizes);
        for (vector<TextureMap::tGroup>::iterator pos = sizes.begin(); pos != sizes.end(); ++pos)
            debugWrite("  %d of %d x %d textures", (*pos).qty, (*pos).texW, (*pos).texH);
    }
}
#endif

const char* helpText[] = {
    "debuglevel",
        "Sets or views current debugging level. Each level outputs various details"
        "on current activities to the console window. Multiple levels may be added"
        "together. Available levels:",
        "  0 - No debugging output",
        "  1 - Undo",
        "  2 - Compilation",
        "  4 - Tokenization",
        "  8 - Bytecode",
#ifdef NDEBUG
#ifdef MEMDEBUG
        "  16- Memory (outputs to stdout.txt only)",
        "  32- Trace (outputs to stdout.txt only)",
#endif
#else
#ifdef MEMDEBUG
        "  16- Interpreter",
        "  32- Memory (outputs to stdout.txt only)",
        "  64- Trace (outputs to stdout.txt only)",
#else
        "  16- Interpreter",
#endif
#endif
        NULL,
    "clear",
        "Clears the console buffer.",
        NULL,
    "compile",
        "Recompiles the named script, displaying all warnings and errors to the "
        "console window.",
        NULL,
    "config",
        "Views or sets configuration settings. Type 'config' alone to list all "
        "possible settings, and their current values. Type 'config' followed by "
        "a setting name to view a single current setting. Type 'config' followed "
        "by a setting and a new value to change a setting.",
        NULL,
    "decompile",
        "Disassemble a script's bytecode and output to the console window.",
        NULL,
    "help",
        "Retrieves help on a specific command. Type 'help' alone to list all "
        "available commands. Type 'help' followed by a command name to get help "
        "on that specific command.",
        NULL,
    "opengltest",
        "Runs various OpenGL tests and reports on the results. "
        "(WARNING: GCSx MAY STOP RESPONDING FOR UP TO A MINUTE)",
        NULL,
    "undobuffer",
        "Sets or views the size of the undo buffer for the active world, in K. An "
        "undo buffer of 0K will disable undo functionality. Changing the buffer "
        "size clears all undo/redo for that world.",
        NULL,
    "world",
        "Sets or views active world. Many commands only operate on a single, open "
        "world. Type 'world' alone to list all available worlds and the currently "
        "active world. Type 'world' followed by a number to select the active world "
        "to use for commands.",
        NULL,
#ifdef MEMDEBUG
    "memexcept",
        "Sets a level above which all memory allocations will fail, for testing "
        "purposes. Specify a number in bytes, or 0 to disable this feature. NOTE: "
        "most failed memory allocations will result in GCSx shutting down.",
        NULL,
#endif
#ifndef NDEBUG
    "texturetest",
        "Follow with a width and height in pixels, and a count. This will attempt "
        "to arrange the given number of graphics, of the given size, and return "
        "how many textures of what size will be used, based on current settings. "
        "Intented solely for debugging texture allocation algorithms.",
        NULL,
    "undoexcept",
        "Sets a number of actions at which all undo storage attempts will fail, "
        "for testing purposes. For example, 2 actions will cause an undo warning "
        "when the second action is attempted. Specify a number of actions, or 0 "
        "to disable this feature. NOTE: Some operations cause multiple undo "
        "actions.",
        NULL,
#endif
    NULL,
};

vector<string>* allCommandsList = NULL;
#define HELP_COLUMNS    4

void cHelpAll(ConsoleParam* params) { start_func
    debugWrite("Available commands:");
    
    if (!allCommandsList) {
        set<string> allCommands;
        unsigned int maxCommandLength = 0;
        
        allCommandsList = new vector<string>;

        // Sort, count, and size all commands
        for (int cmdnum = 0; commands[cmdnum].cmd; ++cmdnum) {
            if (strlen(commands[cmdnum].cmd) > maxCommandLength) {
                maxCommandLength = strlen(commands[cmdnum].cmd);
            }
            allCommands.insert(commands[cmdnum].cmd);
        }
        
        // Number of rows?
        int rows = allCommands.size() / HELP_COLUMNS + ((allCommands.size() % HELP_COLUMNS) ? 1 : 0);
        allCommandsList->reserve(rows);

        // Generate columns (not particularly efficient, but who cares?)
        int spot = 0;
        int firstTrip = 1;
        for (set<string>::iterator pos = allCommands.begin(); pos != allCommands.end(); ++pos) {
            if (firstTrip) allCommandsList->push_back(*pos);
            else (*allCommandsList)[spot] += *pos;
            unsigned int pad = (*pos).length();
            while (pad++ <= maxCommandLength) {
                (*allCommandsList)[spot] += " ";
            }
            if (++spot >= rows) {
                firstTrip = 0;
                spot = 0;
            }
        }
    }
    
    for (vector<string>::iterator pos = allCommandsList->begin(); pos != allCommandsList->end(); ++pos) {
        debugWrite("  %s", (*pos).c_str());
    }
    
    debugWrite("Type 'help <command>' for details");
}

void cHelpCommand(ConsoleParam* params) { start_func
    // Lowercase
    toLower(params[0].valueS);
    
    for (int pos = 0; helpText[pos]; ++pos) {
        if (!strcmp(helpText[pos], params[0].valueS.c_str())) {
            for (++pos; helpText[pos]; ++pos) {
                debugWrite(helpText[pos]);
            }
            return;
        }

        for (++pos; helpText[pos]; ++pos) ;
    }
    
    debugWrite("No help available for '%s', type 'help' for a list of commands.", params[0].valueS.c_str());
}

// Console parser
void consoleParse(const string& cmd) { start_func
    // Skip any leading spaces
    string::size_type pos = 0;
    pos = cmd.find_first_not_of(' ', pos);
    // (only spaces present)
    if (pos == string::npos) return;
    
    // Grab command- text up to next space or end of line
    string::size_type paramstart = cmd.find_first_of(' ', pos);
    string command = cmd.substr(pos, paramstart == string::npos ? string::npos : paramstart - pos);
    // (find start of parameters too)
    paramstart = cmd.find_first_not_of(' ', paramstart);

    // Lowercase
    toLower(command);
    
    // Find command (may be more than one match)
    int found = 0;
    ConsoleParam params[MAX_PARAM];
    for (int cmdnum = 0; commands[cmdnum].cmd; ++cmdnum) {
        if (!strcmp(command.c_str(), commands[cmdnum].cmd)) {
            found = 1;
        
            // Check parameters
            string::size_type parampos = paramstart;
            int invalid = 0;
            for (int paramnum = 0; paramnum < MAX_PARAM; ++paramnum) {
                if (commands[cmdnum].param[paramnum] == PARAM_NONE) {
                    if (parampos != string::npos) invalid = 1;
                    // (no more params)
                    break;
                }
                else if (parampos == string::npos) {
                    invalid = 1;
                }
                else {
                    string::size_type paramend = cmd.find_first_of(' ', parampos);
                    if (commands[cmdnum].param[paramnum] == PARAM_LINE) paramend = string::npos;
                    params[paramnum].valueS = cmd.substr(parampos, paramend == string::npos ? string::npos : paramend - parampos);
                    parampos = cmd.find_first_not_of(' ', paramend);
                    params[paramnum].valueI = 0;
                    
                    // More specific types?
                    if (commands[cmdnum].param[paramnum] == PARAM_INT) {
                        try {
                            params[paramnum].valueI = strToIntErr(params[paramnum].valueS);
                        }
                        catch (int) {
                            invalid = 1;
                        }
                    }
                }
            }
            
            if (!invalid) {
                // Found match!
                if ((!commands[cmdnum].editorMode) && (getProgramMode() == MODE_EDITOR)) {
                    debugWrite("Sorry, '%s' is only available in gameplay mode.", command.c_str());
                }
                else if ((!commands[cmdnum].gameMode) && (getProgramMode() != MODE_EDITOR)) {
                    debugWrite("Sorry, '%s' is only available in editor mode.", command.c_str());
                }
                else {
                    commands[cmdnum].function(params);
                }
                return;
            }
        }
    }
    
    // Not found or invalid parameters
    if (found) {
        // Invalid
        for (int cmdnum = 0; commands[cmdnum].cmd; ++cmdnum) {
            if (!strcmp(command.c_str(), commands[cmdnum].cmd)) {
                string usage;
                for (int paramnum = 0; paramnum < MAX_PARAM; ++paramnum) {
                    switch (commands[cmdnum].param[paramnum]) {
                        case PARAM_NONE:
                            paramnum = MAX_PARAM;
                            break;
                            
                        case PARAM_STRING:
                            usage += " <string>";
                            break;

                        case PARAM_INT:
                            usage += " <integer>";
                            break;

                        case PARAM_LINE:
                            usage += " <string...>";
                            break;
                    }
                }
                debugWrite("Usage: %s%s", command.c_str(), usage.c_str());
            }
        }
        debugWrite("Type 'help %s' for details", command.c_str());
    }
    else {
        debugWrite("Unrecognized command '%s', type 'help' for help", command.c_str());
    }
}

// Cleanup
void destroyConsoleGlobals() { start_func
    if (allCommandsList) {
        delete allCommandsList;
        allCommandsList = NULL;
    }
}

